﻿//*** Guidance generated code ***//
using System;
using System.Collections.Generic;

using System.Linq;
using System.Linq.Expressions;

using ProjectBase.Core;

using $prjsn$.Common;
using $prjsn$.Common.Validation;

namespace $safeprojectname$
{
    public class ValueTypeNullableRule<TItem, TValue> : RuleBase, IValidationRule<TItem>
        where TItem : class
        where TValue : struct, IComparable<TValue>
    {
        #region members

        private readonly Func<TItem, TValue?> _compiledExpression;

        #endregion members

        #region Constructor

        public ValueTypeNullableRule(Expression<Func<TItem, TValue?>> expression)
        {
            var body = expression.Body as MemberExpression;
            if (body.IsNull())
            {
                throw new NotSupportedException(
                    "Provided 'Expression<Func<TItem, TValue?>>' must be of a 'ExpressionType.MemberAccess' (ValueTypeNullableRule). ");
            }
            PropertyName = body.Member.Name;
            _compiledExpression = expression.Compile();
        }

        public ValueTypeNullableRule(Func<TItem, TValue?> compiledExpression, string propertyName)
        {
            PropertyName = propertyName;
            _compiledExpression = compiledExpression;
        }

        #endregion Constructor

        #region Validate

        public virtual ValidationResult Validate(TItem item, out ValidationMessage message)
        {
            message = null;

            // item cannot be null
            if (item.IsNull())
            {
                message = new ValidationMessage(Str.Messages.NullInsteadOfEntityProvided1)
                {
                    EntityName,
                };
                return ValidationResult.Error;
            }

            var result = CompiledExpression.Invoke(item);
            var entity = item as IPersistentObject;
            var itemId = entity.IsNull()
                ? "N/A"
                : entity.IsTransient() ? "new" : entity.IDToDisplay;

            // NOT Required string could be null or empty
            if (!result.HasValue)
            {
                if (!IsRequired)
                {
                    return ValidationResult.IsValid;
                }

                message = new ValidationMessage(Str.Messages.MissingProperty2)
                {
                    itemId,
                    PropertyName,
                };
                return ValidationResult.Error;
            }

            // is too short
            var isShorter = MinValue.CompareTo(result.Value) > 0;
            if (isShorter)
            {
                message = new ValidationMessage(Str.Messages.ValueTooLow4)
                {
                    PropertyName.GetLocalized(EntityName),
                    itemId,
                    result.Value,
                    MinValue,
                };
                return ValidationResult.Error;
            }

            // is too long
            var isLonger = MaxValue.CompareTo(result.Value) < 0;
            if (isLonger)
            {
                message = new ValidationMessage(Str.Messages.ValueTooHigh4)
                {
                    PropertyName.GetLocalized(EntityName),
                    itemId,
                    result.Value,
                    MaxValue,
                };
                return ValidationResult.Error;
            }

            return ValidationResult.IsValid;
        }

        #endregion Validate

        #region fluent

        public virtual ValueTypeNullableRule<TItem, TValue> AsRequired()
        {
            IsRequired = true;
            return this;
        }

        public virtual ValueTypeNullableRule<TItem, TValue> AllowNull()
        {
            IsRequired = false;
            return this;
        }

        public virtual ValueTypeNullableRule<TItem, TValue> LimitValueTo(TValue upper)
        {
            MaxValue = upper;
            return this;
        }

        public virtual ValueTypeNullableRule<TItem, TValue> RequireValueBetween(TValue lower, TValue upper)
        {
            MinValue = lower;
            MaxValue = upper;
            return this;
        }

        public virtual ValueTypeNullableRule<TItem, TValue> RequireMinValue(TValue lower)
        {
            MinValue = lower;
            return this;
        }

        public virtual ValueTypeNullableRule<TItem, TValue> RequireMaxValue(TValue upper)
        {
            MaxValue = upper;
            return this;
        }

        #endregion fluent

        #region Client

        public override string GetClientRule(string prefix = null)
        {
            var rule = string.Empty;

            if (Ext.ValidatedStructs.Contains(typeof(TValue)))
            {
                rule = Templates.Number.FormatWith(prefix.Extend(PropertyName),
                    IsRequired.ToString().ToLowerInvariant(),
                    MinValue.ToString(),
                    MaxValue.ToString())
                    ;
            }
            return rule;
        }

        public override IMDValidationRule GetMetaDataRule()
        {
            var type = typeof(TValue);

            if (type.IsEquivalentTo(typeof(bool)))
            {
                return new MDMandatoryRule
                {
                    IsRequired = IsRequired
                };
            }

            var rule = new MDValueTypeRule<TValue>
            {
                IsRequired = IsRequired,
                MinValue = MinValue,
                MaxValue = MaxValue,
            };

            if (type.IsEquivalentTo(typeof(DateTime)))
            {
                var minDate = ((DateTime)(object)MinValue).ToUniversalTime();
                var maxDate = ((DateTime)(object)MaxValue).ToUniversalTime();
                rule.MinValue = (TValue)(object)minDate;
                rule.MaxValue = (TValue)(object)maxDate;
            }

            return rule;
        }

        public override string GetClientMessage(string prefix = null)
        {
            return string.Empty;
        }

        #endregion Client

        #region properties

        protected virtual Func<TItem, TValue?> CompiledExpression { get { return _compiledExpression; } }

        protected virtual TValue MinValue { get; set; }

        protected virtual TValue MaxValue { get; set; }

        protected virtual bool IsRequired { get; set; }

        protected virtual string EntityName { get { return typeof(TItem).Name; } }

        #endregion properties
    }
}